package service

import (
	"fmt"
	"time"
	"net/http"
	"encoding/json"
	"encoding/base64"

	"github.com/go-xorm/xorm"

	"read.com/wxmp/api"
	"read.com/wxmp/dao"
	"read.com/wxmp/util"
	"read.com/wxmp/crypto"
)

type WechatService struct {
	db     *xorm.Session
	app    *dao.SysApp
	openId string

	user *dao.UserUnion
}

type UrlMenu struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Url  string `json:"url"`
}

type SubUrlMenu struct {
	Name string    `json:"name"`
	List []UrlMenu `json:"sub_button"`
}

func NewWechatService(db *xorm.Session, app *dao.SysApp, openId string) *WechatService {
	return &WechatService{
		db:     db,
		app:    app,
		openId: openId,
	}
}

func (s *WechatService) Close() {
	if s.db != nil {
		s.db.Close()
	}
	s.db = nil
	s.app = nil
	s.user = nil
}

// 创建菜单
func CreateWxMenu(app *dao.SysApp) {
	accessToken := api.GetAccessToken(app.Id)
	if accessToken == "" {
		fmt.Println("access token 未生成.", app.AppName)
		return
	}

	menuList := []interface{}{
		UrlMenu{
			Name: "继续阅读",
			Type: "view",
			Url:  app.GetAppUrl("/view/home/menu/track/"), //阅读足迹
		},
		UrlMenu{
			Name: "免费书城",
			Type: "view",
			Url:  app.GetAppUrl("/view/home/auth/"), //书城入口
		},
		SubUrlMenu{
			Name: "用户中心",
			List: []UrlMenu{
				{
					Name: "个人中心",
					Type: "view",
					Url:  app.GetAppUrl("/view/home/menu/mine/"),
				},
				{
					Name: "赠送书币",
					Type: "view",
					Url:  app.GetAppUrl("/view/home/menu/sign/"), //签到
				},
				{
					Name: "我要充值",
					Type: "view",
					Url:  app.GetAppUrl("/view/home/menu/rechargebook/"),
				},
				{
					Name: "开通VIP",
					Type: "view",
					Url:  app.GetAppUrl("/view/home/menu/mineVip/"),
				},
			},
		},
	}

	menu := map[string]interface{}{
		"button": menuList,
	}
	menuData, err := json.Marshal(menu)
	if err != nil {
		fmt.Println("create menu error.", err, app.AppName)
		return
	}

	urlStr := "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken
	if data, err := api.PostJsonRequest(urlStr, menuData); err != nil {
		fmt.Println("菜单创建错误:", err, app.AppName)
	} else {
		res := &api.WechatError{}
		if err = json.Unmarshal(data, res); err != nil {
			fmt.Println("菜单创建，解析错误:", err, string(data), app.AppName)
			return
		} else if res.ErrCode != 0 {
			fmt.Println("菜单创建失败:", string(data), app.AppName)
			return
		}
		fmt.Println("菜单创建成功:", app.AppName)
	}
}

// 被动回复消息（根据用户发送的消息检索小说，以被动回复消息的方式回复用户）
func (s *WechatService) ReplyMsg(w http.ResponseWriter, content, nonce, timestamp string) bool {
	if s.user == nil {
		return false
	}
	replyMsg := util.NewStringBuilder("")
	bookList := dao.BookQueryLike(content)
	if len(bookList) > 0 {
		replyMsg.AppendString("找到*").AppendString(content).AppendString("*的相关作品如下：\n\n")
	} else {
		replyMsg.AppendString("暂未找到*").AppendString(content).AppendString("*的相关作品，\n立即看近期热门小说：\n\n")
		bookList = dao.GetHotBook(s.user.Sex)
	}
	i := 0
	urlStr := ""
	for ; i < len(bookList); i++ {
		urlStr = s.app.GetAppUrl("/view/home/book/" + bookList[i].SerialId + "/")
		replyMsg.AppendInt(i + 1).AppendString("、").AppendHtmlHref(urlStr, bookList[i].Name).AppendString("\n\n")
	}
	urlStr = s.app.GetAppUrl("/view/home/menu/listdetail/") + "?type=热门小说"
	replyMsg.AppendInt(i + 1).AppendString("、").AppendHtmlHref(urlStr, "更多作品......")

	responseEncryptTextBody, err := crypto.MakeEncryptResponseBody(s.app.DevOriginal, s.openId, replyMsg.String(), nonce, timestamp, s.app.DevToken, s.app.DevId, s.app.DevMsgKey)
	if err == nil {
		w.Header().Set("Content-type", "text/xml")
		w.Write(responseEncryptTextBody)
		return true
	}
	return false
}

// 关注
func (s *WechatService) Subscribe(byQrCode bool) {
	accessToken := api.GetAccessToken(s.app.Id)
	if accessToken == "" {
		fmt.Println("AccessToken is null, app=", s.app.Id)
		if s.user == nil {
			return
		}
	}

	now := time.Now()
	s.db.Begin()

	has := s.user != nil
	if has {
		// 已关注用户，不触发回复
		if s.user.Subscribe == 0 {
			// 更新用户信息
			if affected, err := s.db.
				Cols("subscribe", "subscribe_time").
				ID(s.user.Id).
				Update(&dao.UserUnion{
				Subscribe:     1,
				SubscribeTime: now,
			}); err != nil || affected == 0 {
				fmt.Println("用户关注更新失败:", err, affected)
				s.db.Rollback()
				return
			} else {
				s.user.Subscribe = 1
				s.user.SubscribeTime = now
			}
		}
	} else if success := s.userReg(accessToken); !success { // 新用户
		s.db.Rollback()
		return
	}

	// 主动回复用户
	if accessToken != "" {
		builder := util.NewStringBuilder("你终于来啦~~\n")
		showContinueRead := false
		if !has && byQrCode {
			// 查询书城用户
			userId := GetUserId(s.db, s.user.UnionId)
			if userId != "" {
				// 首次关注，赠送100书币
				s.db.InsertOne(&dao.LogBill{
					UserId:   userId,
					AppId:    s.app.Id,
					BillType: 4,
					ProdCost: 0,
					ProdGain: 1,
					Cost:     0,
					Gain:     100,
					BillDate: now,
					Title:    "关注公众号",
				})
				// 是否阅读过
				count := GetBookReadCount(s.db, userId)
				showContinueRead = count > 0
			}
		}
		builder.AppendString("关注并置顶").AppendString(s.app.AppName).AppendString("，每日免费看小说！\n\n")
		if showContinueRead {
			builder.AppendString("📖").AppendHtmlHref(s.app.GetAppUrl("/view/home/last/chap/"), "点我继续阅读").AppendString("\n\n")
		}
		// 轮播位推荐
		bookList := dao.GetBannerBookByUserSex(s.user.Sex)
		builder.AppendString("更多精彩小说推荐：\n")
		for i := 0; i < len(bookList); i++ {
			urlStr := s.app.GetAppUrl("/view/home/book/" + bookList[i].SerialId + "/")
			builder.AppendInt(i + 1).AppendString("、").AppendHtmlHref(urlStr, bookList[i].Name).AppendString("\n")
		}
		builder.AppendString("\n客服温馨提示：\n")
		builder.AppendString("1）在下方消息输入框内输入【书名】或任意关键字并发送，点击回复的蓝色字，即可直接阅读。\n")
		builder.AppendString("2）点击").AppendHtmlHref(s.app.GetAppUrl("/view/home/menu/track/"), "最近阅读").AppendString("，查找阅读记录更方便哦！\n")
		builder.AppendString("3）如果遇到充值或其他问题，请联系客服，客服QQ：").AppendString(s.app.ServiceQq).AppendString("\n\n")
		builder.AppendString("🌹🌹陪伴，是最长情的告白，愿我们一生不离不弃。")
		go api.SendCustomMessage(accessToken, s.openId, builder.String())
	}
	s.db.Commit()
}

// 取消关注
func (s *WechatService) UnSubscribe() {
	if s.user == nil {
		return
	}
	// 更新用户信息
	affected, err := s.db.
		Cols("subscribe", "unsubscribe_time").
		ID(s.user.Id).
		Update(&dao.UserUnion{
		Subscribe:       0,
		UnsubscribeTime: time.Now(),
	})
	if err != nil || affected == 0 {
		fmt.Println("取消关注，用户更新失败:", err, affected, s.openId, s.app.Id)
	}
}

func (s *WechatService) InitUser() {
	if s.user != nil {
		return
	}
	user := &dao.UserUnion{
		AppId:  s.app.Id,
		OpenId: s.openId,
	}
	if has, err := s.db.Get(user); err != nil {
		fmt.Println("get user_union error.", err, s.app.Id, s.openId)
		return
	} else if has {
		s.user = user
	} else {
		// 补丁1:漏注册的用户
		if accessToken := api.GetAccessToken(s.app.Id); accessToken != "" {
			s.userReg(accessToken)
			return
		}
	}
	now := time.Now()

	// 是否今天首次交互
	updateObj := &dao.UserUnion{
		OnlineDate: now,
	}
	if now.Format("2006-01-02") == user.LoginDate.Format("2006-01-02") {
		s.db.Cols("online_date")
	} else {
		updateObj.LoginDate = now
		s.db.Cols("online_date", "login_date")
	}
	if affected, err := s.db.ID(user.Id).Update(updateObj); err != nil || affected == 0 {
		fmt.Println("update user_union fail.", err, affected, user.AppId, user.OpenId)
	}
}

func (s *WechatService) userReg(token string) bool {
	// 查询微信用户信息
	userInfo := api.RequestUserInfo(token, s.openId)
	if userInfo == nil {
		fmt.Println("wechat userinfo not found. openId=", s.openId)
		return false
	}
	subscribeTime := time.Unix(userInfo.SubscribeTime, 0)
	now := time.Now()
	userUnion := &dao.UserUnion{
		AppId:         s.app.Id,
		OpenId:        s.openId,
		UnionId:       userInfo.UnionId,
		Subscribe:     1,
		Nickname:      base64.StdEncoding.EncodeToString([]byte(userInfo.Nickname)),
		Avatar:        userInfo.Avatar,
		Sex:           userInfo.Sex,
		Country:       userInfo.Country,
		Province:      userInfo.Province,
		City:          userInfo.City,
		SubscribeTime: subscribeTime,
		OnlineDate:    now,
		LoginDate:     now,
		CreateDate:    now,
	}
	if affected, err := s.db.Nullable("unsubscribe_time").InsertOne(userUnion); err != nil || affected == 0 {
		fmt.Println("新用户插入失败:", err, affected, s.app.Id, s.openId)
		return false
	}
	s.user = userUnion
	return true
}
